// ----------------
// Project:
// ESA Elevator
// ----------------
//
// ----------------
// Group: 163
// 2358569: Pascal Klink
// 2649456: Lukas Kreh 
// ----------------
//
// Description:
// ----------------
// elevator cabin-control
//
// Version History:
// ----------------
// 140116: set parameter FLOOR_BITS to 7

`timescale 1ns / 1ns

module cabin_ctrl

          #(parameter FLOOR_BITS             =        7, // 
                      CONTROL_STEP_DISTANCE  =       10, // [mm]
                      DISTANCE_BITS_BUILDING =       19, // 297.000 mm
                      DISTANCE_BITS_FLOORS   =       12, // 12 bits <> 3000mm, required for height counter
                      FLOOR_HEIGHT           =     3000, // [mm]
                      WAIT_CYCLE             = 40000000, // wait 40 Mio cycles for a delay of 2 seconds @ 20 MHz 
                      WAIT_CYCLE_BITS        =       26) // requires 26 bits


          (input  wire                                CLK,
           input  wire                                RESET,
                                                       
           input  wire                                MANUAL_DOOR_CLOSE,     // close
           input  wire                                MANUAL_DOOR_OPEN,      // open
           input  wire                                MANUAL_ALARM,          // alarm
           input  wire                                OBJECT_DETECTED,       // object
           input  wire                                SPEED_OK    ,          // accel_ok
           input  wire                                WEIGHT_OK,             // weight_ok
           input  wire                                SMOKE_DETECTED,        // alarm
           input  wire [(FLOOR_BITS-1)            :0] NEXT_FLOOR,            // next floor to visit
           input  wire                                DOOR_MOTOR_DONE,       // done 
           input  wire                                ELEVATOR_MOTOR_DONE,   // done
           input  wire                                ELEVATOR_MOTOR_TICK,   // Elevatormotor drove 10mm
                      
           output reg  [(FLOOR_BITS-1)            :0] CURRENT_FLOOR,
           output reg                                 HALTED,                // halted at floor
           output wire                                OPEN_DOOR,             // open
           output wire                                CLOSE_DOOR,            // close
           output wire                                ELEVATOR_UP,           // up
           output wire                                ELEVATOR_DOWN,         // down
           output reg  [(DISTANCE_BITS_BUILDING-1):0] DISTANCE,              // go distance in milimeter
           output reg                                 EMERGENCY_BRAKE);      // emergency break cmd

/* =============================INSERT CODE HERE======================================*/ 

	//Local parameters for clarifying the meaning of the state values
	localparam waiting				= 0;
	localparam door_closing			= 1;
	localparam moving					= 2;
	localparam door_opening			= 3;
	localparam resolved				= 4;
	
	//Register for storing the state of the module
	reg [2:0] 										state;
	//Register for realizing the wait intervals before closing the door and reopening the door
	reg [WAIT_CYCLE_BITS-1:0]					door_close_wait_counter;
	//Helper used to check whether the elevator has to go up or down to reach the next floor
	wire 												up;
	//The distance between NEXT_FLOOR and CURRENT_FLOOR in floors
	wire [DISTANCE_BITS_BUILDING - 1:0]		distance_in_mm;
	//Register for storing the value of NEXT_FLOOR when the module gave the instructions to the elevator module 
	reg [FLOOR_BITS - 1:0]						initial_target;
	//Register for storing the control steps being done (recieved from the elevator motor)
	reg [DISTANCE_BITS_FLOORS - 1:0]			control_step_count;
	/*
	* Helper used to set the motor signals for two clock cycles and wait another two clock cycles before checking
	* if the motor has finished
	*/
	reg [1:0]										signal_sent;
	//Helper used to ensure that control steps recieved from the elevator motor are only counted once
	reg												duplicate;
	/*
	* Helpers used to be able to set the steering signals for the motor within an always block (would not be possible
	* with wires)
	*/
	reg												close_door_reg, open_door_reg, elevator_up_reg, elevator_down_reg;
	
	/*******************
	* Sequential Logic *
	*******************/
	
	always @ (posedge CLK) begin
		/*
		* This logic block ensures that motor steering signals are high for two clock cycles
		* (signal_sent is set from 0 to 1) and that another two clock cycles pass by before 
		* done signals from the motors are evaluated (singal_sent is set from 2 to 3)
		*/
		if({open_door_reg, close_door_reg, elevator_up_reg, elevator_down_reg} != 0) begin
			if(signal_sent == 1) begin
				{open_door_reg, close_door_reg, elevator_up_reg, elevator_down_reg} <= 4'b0;
				signal_sent									<= 2;
			end
			else begin
				signal_sent 								<= 1;
			end
		end
		if(signal_sent == 2)begin
			signal_sent										<= 3;
		end
		if(RESET) begin
			CURRENT_FLOOR									<= 0;
			state												<= 0;
			door_close_wait_counter						<= 1;
			open_door_reg									<= 0;
			close_door_reg									<= 0;
			elevator_up_reg								<= 0;
			elevator_down_reg								<= 0;
			DISTANCE											<= 0;			
			EMERGENCY_BRAKE								<= 0;
			control_step_count							<= 0;
			signal_sent										<= 0;
		end
		//Do an emergency break if the cabin is falling
		else if(!SPEED_OK) begin
			EMERGENCY_BRAKE								<= 1;
			elevator_up_reg								<= 1;
			elevator_down_reg								<= 1;
		end
		/*
		* Only process inputs if no emergency break was triggered (if the emergency break
		* was triggered, then a reset is necessary)
		*/
		else if(!EMERGENCY_BRAKE) begin
			case (state)
				/*
				* Start to wait for the given interval (WAIT_CYCLE) if the cabin should move
				* to a new floor (NEXT_FLOOR != CURRENT_FLOOR). If an object is detected during the wait cycle
				* (!OBJECT_DETECTED) then the counter is reseted and the module starts waiting for the full interval again. 
				* If the module has waited for the given interval, start to close the door (close_door_reg <= 1) if no other
				* alarms are currently occuring (& SPEED_OK & !SMOKE_DETECTED & !MANUAL_ALARM & WEIGHT_OK) - The
				* wait interval can be skipped by pressign MANUAL_DOOR_CLOSE
				*/
				waiting: begin
					if(NEXT_FLOOR != CURRENT_FLOOR) begin
						if((door_close_wait_counter == WAIT_CYCLE | MANUAL_DOOR_CLOSE) & SPEED_OK & !SMOKE_DETECTED & !MANUAL_ALARM & WEIGHT_OK) begin
							state 							<= door_closing;
							door_close_wait_counter		<= 1;
							close_door_reg					<= 1;
							signal_sent						<= 0;
						end
						else if(!OBJECT_DETECTED)begin
							door_close_wait_counter 	<= door_close_wait_counter + 1;
						end
						else begin
							door_close_wait_counter		<= 1;
						end
					end
				end
				/*
				* If the motor module had enough time to process the steering signal (signal_sent == 3)
				* and the door is closed (DOOR_MOTOR_DONE), start to move the cabin. If an object is detected
				* or MANUAL_DOOR_OPEN is pressed, while closing the door, stop closing the door and 
				* go to state "resolved"
				*/
				door_closing: begin
					if(signal_sent == 3 & DOOR_MOTOR_DONE) begin	
						state									<= moving;
						DISTANCE								<=	distance_in_mm;
						initial_target						<= NEXT_FLOOR;
						elevator_up_reg					<= up;
						elevator_down_reg					<= !up;
						signal_sent							<= 0;
					end
					else if(OBJECT_DETECTED | MANUAL_DOOR_OPEN) begin
						close_door_reg						<= 1;
						open_door_reg						<= 1;
						state									<= resolved;
						signal_sent							<= 0;
					end
				end
				/*
				* In this state, the module only waits for the given interval (WAIT_CYCLE),
				* so that the door motor can completely stop. If the wait interval has been
				* passed, open the door (open_door_reg <= 1)
				*/
				resolved: begin
					if(door_close_wait_counter == WAIT_CYCLE) begin
						state									<= door_opening;
						open_door_reg						<= 1;
						signal_sent							<= 0;
						door_close_wait_counter			<= 1;
					end
					else begin
						door_close_wait_counter			<= door_close_wait_counter + 1;
					end
				end
				/*
				* Note: In this state, the module only processes the input ELEVATOR_MOTOR_TICK every two clock cycles,
				* since it runs twice as fast as the motor module. This is realized with the "duplicate"
				* register.
				* While the cabin is moving, all control steps are counted and if enough control_steps have
				* been done, the CURRENT_FLOOR is updated (control_step_count + CONTROL_STEP_DISTANCE == FLOOR_HEIGHT).
				* If the cabin has reached it's target (signal_sent == 3 & ELEVATOR_MOTOR_DONE), then the door is opened.
				* If an update is recieved, the new distance is given to the motor (DISTANCE <= distance_in_mm) when
				* reaching a new floor and initial_target is updated to the new target floor.
				*/
				moving: begin
					if(ELEVATOR_MOTOR_TICK) begin
						if(duplicate) begin
							duplicate 					<= 0;
						end
						else begin
							duplicate						<= 1;
							if(control_step_count + CONTROL_STEP_DISTANCE == FLOOR_HEIGHT) begin
								control_step_count 		<= 0;
								CURRENT_FLOOR				<= up ? CURRENT_FLOOR + 1 : CURRENT_FLOOR - 1;
								if(NEXT_FLOOR != initial_target) begin
									DISTANCE					<=	distance_in_mm;
									initial_target			<= NEXT_FLOOR;
									elevator_up_reg		<= up;
									elevator_down_reg		<= !up;	
									signal_sent				<= 0;
								end
							end
							else begin
								control_step_count		<= control_step_count + CONTROL_STEP_DISTANCE;
							end
						end
					end
					else if(signal_sent == 3 & ELEVATOR_MOTOR_DONE) begin
						state								<= door_opening;
						open_door_reg					<= 1;
						signal_sent						<= 0;
					end
				end
				/*
				* If the motor module had enough time to process the steering signal (signal_sent == 3)
				* and the door is open (DOOR_MOTOR_DONE), go to state "waiting".
				*/
				door_opening: begin
					if(signal_sent == 3 & DOOR_MOTOR_DONE) begin
						state									<= waiting;
					end
				end
				default: begin
					state										<= state;
				end
			endcase
		end
	end
	
	/**********************
	* Combinational Logic *
	**********************/
	
	//Set HALTED to high, if cabin is not moving
	always @ (*) begin
		HALTED = state != moving;
	end
	
	/*
	* Caclulation of up and distance_in_mm (for the latter, it makes a difference, if an updated distance is calculated,
	* or not. An update is calculated when reaching a new floor, the problem is, that when reaching the new floor the updated
	* value for CURRENT_FLOOR is not in the register when calculating the new distnace, consequently we have to take this into 
	* account by adding/subtracting 1 from the CURRENT_FLOOR
	*/
	assign up = NEXT_FLOOR > CURRENT_FLOOR;
	assign distance_in_mm = (state != moving) ? (up ? NEXT_FLOOR - CURRENT_FLOOR : CURRENT_FLOOR - NEXT_FLOOR) * FLOOR_HEIGHT :
															  (up ? (NEXT_FLOOR - (CURRENT_FLOOR + 1)) : ((CURRENT_FLOOR - 1) - NEXT_FLOOR)) * FLOOR_HEIGHT;
	
	//Assign the helper values to the real outputs
	assign OPEN_DOOR = open_door_reg;
	assign CLOSE_DOOR = close_door_reg;
	assign ELEVATOR_UP = elevator_up_reg;
	assign ELEVATOR_DOWN = elevator_down_reg;

/* ====================================================================================*/ 

endmodule
